﻿using Spring.Objects.Factory.Xml;
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spring.Objects.Factory.Config;
using System.Xml;
using Common.Logging;
using Spring.Objects.Factory.Support;
using Dubbo.Config.Api;
using Spring.Objects;
using Dubbo.Config.Spring;

namespace Dubbo.Config.Spring.Schema
{
    public class DubboObjectDefinitionParser : IObjectDefinitionParser
    {
        private static ILog logger = LogManager.GetLogger(typeof(DubboObjectDefinitionParser));

        private readonly Type objectType;

        private readonly bool required;

        public DubboObjectDefinitionParser(Type objectType, bool required)
        {
            this.objectType = objectType;
            this.required = required;
        }

        public IObjectDefinition ParseElement(XmlElement element, ParserContext parserContext)
        {
            return Parse(element, parserContext, objectType, required);
        }

        private static bool IsPrimitive(Type type)
        {
            return type.IsPrimitive || type == typeof(bool) || type == typeof(byte)
                || type == typeof(char) || type == typeof(short) || type == typeof(int)
                || type == typeof(long) || type == typeof(float) || type == typeof(double)
                || type == typeof(string) || type == typeof(DateTime) || type == typeof(Type)
                || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        private IObjectDefinition Parse(XmlElement element, ParserContext parserContext, Type objectType, bool required)
        {
            RootObjectDefinition objDefinition = new RootObjectDefinition()
            {
                ObjectType = objectType,
                IsLazyInit = false
            };
            if (typeof(ServiceObject<>) == objectType || typeof(ReferenceObject<>) == objectType)
            {
                var ifArg = element.GetAttribute("interface");
                if (string.IsNullOrEmpty(ifArg))
                {
                    throw new XmlException("interface is required");
                }
                var typeArg = Type.GetType(ifArg);
                objDefinition.ObjectType = objectType.MakeGenericType(typeArg);
            }

            string id = element.GetAttribute("id");
            if (string.IsNullOrEmpty(id) && required)
            {
                string objectName = element.GetAttribute("name");
                if (string.IsNullOrEmpty(objectName))
                {
                    if (typeof(ProtocolConfig) == objectType)
                    {
                        objectName = "dubbo";
                    }
                    else
                    {
                        objectName = element.GetAttribute("interface");
                    }
                }
                if (string.IsNullOrEmpty(objectName))
                {
                    objectName = objectType.Name;
                }
                id = objectName;
                int counter = 2;
                while (parserContext.Registry.ContainsObjectDefinition(id))
                {
                    id = objectName + (counter++);
                }
            }

            if (!string.IsNullOrEmpty(id))
            {
                if (parserContext.Registry.ContainsObjectDefinition(id))
                {
                    throw new XmlException("Duplicate spring bean id " + id);
                }
                parserContext.Registry.RegisterObjectDefinition(id, objDefinition);
                objDefinition.PropertyValues.Add("Id", id);
            }
            if (typeof(ProtocolConfig) == objectType)
            {
                foreach (var name in parserContext.Registry.GetObjectDefinitionNames())
                {
                    var definition = parserContext.Registry.GetObjectDefinition(name);
                    PropertyValue property = definition.PropertyValues.GetPropertyValue("protocol");
                    if (property != null)
                    {
                        object value = property.Value;
                        if (value is ProtocolConfig && id == ((ProtocolConfig)value).Name)
                        {
                            definition.PropertyValues.Add("Protocol", new RuntimeObjectReference(id));
                        }
                    }
                }
            }
            else if (typeof(ServiceObject<>) == objectType)
            {
                string className = element.GetAttribute("class");
                if (!string.IsNullOrEmpty(className))
                {
                    RootObjectDefinition classDefinition = new RootObjectDefinition()
                    {
                        //ObjectType = 
                        IsLazyInit = false
                    };
                    //classDefinition.setBeanClass(ReflectUtils.forName(className));
                    //classDefinition.setLazyInit(false);
                    //parseProperties(element.ChildNodes, classDefinition);
                    //objDefinition.PropertyValues.Add("ref", new ObjectDefinitionHolder(classDefinition, id + "Impl"));
                }
            }
            //else if (typeof(ProviderConfig) == type)
            //{
            //parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, objDefinition);
            //} 
            //else if (ConsumerConfig.class.equals(beanClass)) 
            //{
            //    parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, objDefinition);
            //}

            ISet<string> props = new HashSet<string>();
            ManagedDictionary parameters = null;
            foreach (var setter in objectType.GetMethods())
            {
                string name = setter.Name;
                if (name.Length > 4 && name.StartsWith("set_")
                    && setter.IsPublic && setter.GetParameters().Length == 1)
                {
                    Type type = setter.GetParameters()[0].ParameterType;
                    //string property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
                    string property = name.Substring(4, 1).ToLower() + name.Substring(5);
                    props.Add(property);
                    MethodInfo getter = null;
                    try
                    {
                        getter = objectType.GetMethod("get_" + name.Substring(4));
                        if (getter == null)
                        {
                            getter = objectType.GetMethod("Is" + name.Substring(4));
                        }
                    }
                    catch (Exception ex)
                    {
                    }
                    if (getter == null || !getter.IsPublic || getter.ReturnType != type)
                    {
                        continue;
                    }
                    if ("parameters" == property)
                    {
                        //parameters = parseParameters(element.getChildNodes(), objDefinition);
                    }
                    else if ("methods" == property)
                    {
                        //parseMethods(id, element.getChildNodes(), objDefinition, parserContext);
                    }
                    else if ("arguments" == property)
                    {
                        //parseArguments(id, element.getChildNodes(), objDefinition, parserContext);
                    }
                    else
                    {
                        string value = element.GetAttribute(property);
                        if (value != null)
                        {
                            value = value.Trim();
                            if (value.Length > 0)
                            {
                                if ("registry" == property && RegistryConfig.NO_AVAILABLE.Equals(value, StringComparison.OrdinalIgnoreCase))
                                {
                                    RegistryConfig registryConfig = new RegistryConfig();
                                    registryConfig.Address = RegistryConfig.NO_AVAILABLE;
                                    objDefinition.PropertyValues.Add(property, registryConfig);
                                }
                                else if ("registry" == property && value.IndexOf(',') != -1)
                                {
                                    ParseMultiRef("Registries", value, objDefinition, parserContext);
                                }
                                else if ("provider" == property && value.IndexOf(',') != -1)
                                {
                                    ParseMultiRef("Providers", value, objDefinition, parserContext);
                                }
                                else if ("protocol" == property && value.IndexOf(',') != -1)
                                {
                                    ParseMultiRef("Protocols", value, objDefinition, parserContext);
                                }
                                else
                                {
                                    object reference;
                                    if (IsPrimitive(type))
                                    {
                                        if ("async" == property && "false" == value
                                                || "timeout" == property && "0" == value
                                                || "delay" == property && "0" == value
                                                || "version" == property && "0.0.0" == value
                                                || "stat" == property && "-1" == value
                                                || "reliable" == property && "false" == value)
                                        {
                                            // 兼容旧版本xsd中的default值
                                            value = null;
                                        }
                                        reference = value;
                                    }
                                    else
                                    {
                                        if ("ref" == property && parserContext.Registry.ContainsObjectDefinition(value))
                                        {
                                            var refObject = parserContext.Registry.GetObjectDefinition(value);
                                            if (!refObject.IsSingleton)
                                            {
                                                throw new Exception("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
                                            }
                                        }
                                        reference = new RuntimeObjectReference(value);
                                    }
                                    objDefinition.PropertyValues.Add(property.Substring(0, 1).ToUpper() + property.Substring(1), reference);
                                }
                            }
                        }
                    }
                }
            }

            var attributes = element.Attributes;
            int len = attributes.Count;
            for (int i = 0; i < len; i++)
            {
                XmlNode node = attributes.Item(i);
                string name = node.LocalName;
                if (!props.Contains(name))
                {
                    if (parameters == null)
                    {
                        parameters = new ManagedDictionary();
                    }
                    string value = node.Value;
                    parameters[name] = new TypedStringValue(value, typeof(string));
                }
            }
            if (parameters != null)
            {
                //objDefinition.PropertyValues.Add("Parameters", parameters);
            }

            return objDefinition;
        }

        private static void ParseMultiRef(string property, string value, RootObjectDefinition beanDefinition,
            ParserContext parserContext)
        {
            //String[] values = value.split("\\s*[,]+\\s*");
            string[] values = value.Split(',');
            ManagedList list = null;
            for (int i = 0; i < values.Length; i++)
            {
                string v = values[i];
                if (!string.IsNullOrEmpty(v))
                {
                    if (list == null)
                    {
                        list = new ManagedList();
                    }
                    list.Add(new RuntimeObjectReference(v));
                }
            }
            beanDefinition.PropertyValues.Add(property, list);
        }
    }
}
